package com.raulraja.model.domain;

import com.raulraja.model.persistence.PersistenceAdapter;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.transaction.annotation.Transactional;

import javax.ws.rs.*;
import java.lang.reflect.InvocationTargetException;

/**
 * Base class for entities with static access to the persistent context
 */
public abstract class PersistentEntity<Entity extends PersistentEntity> {

	public static final String NAMESPACE = "https://apps.raulraja.org/api";

	private static PersistenceAdapter<PersistentEntity> persistenceAdapter;

	public static void setPersistenceAdapter(PersistenceAdapter<PersistentEntity> persistenceAdapter) {
		PersistentEntity.persistenceAdapter = persistenceAdapter;
	}

	public abstract Long getId();

	public abstract Class<Entity> getMappedClass();

	@Override
	@SuppressWarnings("unchecked")
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}

		PersistentEntity<?> entity = (PersistentEntity<?>) o;

		if (getId() != null ? !getId().equals(entity.getId()) : entity.getId() != null) {
			return false;
		}

		return true;
	}

	@Override
	public int hashCode() {
		return getId() != null ? getId().hashCode() : 0;
	}


	/**
	 * Returns a paginated list of all entities for this class
	 * @param start the start record
	 * @param size the limit
	 * @return the list of matches
	 */
	@GET @Path("list")
	@SuppressWarnings("unchecked")
    public PaginatedList<Entity> list(@QueryParam("start") int start, @QueryParam("size") int size) {
		Class mappedClass = getMappedClass();
		PaginatedList<Entity> list = (PaginatedList<Entity>) persistenceAdapter.list(mappedClass, start, size);
		return list;
	}

	/**
	 * Returns a paginated list of all entities for this class
	 * @param start the start record
	 * @param size the limit
	 * @return the list of matches
	 */
	@GET @Path("{property}/list")
	@SuppressWarnings("unchecked")
    public PaginatedList<Object> list(@PathParam("property") String property, @QueryParam("start") int start, @QueryParam("size") int size) {
		Class mappedClass = getMappedClass();
		PaginatedList<Object> list = (PaginatedList<Object>) persistenceAdapter.list(mappedClass, property, start, size);
		return list;
	}


	@GET @Path("{id}")
    public PersistentEntity get(@PathParam("id") Long id) {
		Class mappedClass = getMappedClass();
		PersistentEntity entity = persistenceAdapter.get(mappedClass, id);
		return entity;
	}

	@GET @Path("{id}/{property}")
    public Object getProperty(@PathParam("id") Long id, @PathParam("property") String property) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		Class mappedClass = getMappedClass();
		PersistentEntity entity = persistenceAdapter.get(mappedClass, id);
		return PropertyUtils.getProperty(entity, property);
	}

	@POST @Path("{id}/{property}")
	@Transactional
    public Object updateProperty(@PathParam("id") Long id, @PathParam("property") String property, String value) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
		Class mappedClass = getMappedClass();
		PersistentEntity entity = persistenceAdapter.get(mappedClass, id);
		BeanUtils.setProperty(entity, property, value);
		return persistenceAdapter.save(entity);	
	}

	@POST @Path("create")
	@Transactional
    public PersistentEntity create(Entity entity) {
		PersistentEntity persistentEntity = get(entity.getId());
		if (persistentEntity != null) {
			throw new IllegalStateException("There is already a " + getMappedClass() + " with id = " + entity.getId());
		}
		persistenceAdapter.save(entity);
		return entity;
	}

	@POST @Path("update")
	@Transactional
    public PersistentEntity update(Entity entity) {
		PersistentEntity<Entity> persistentEntity = get(entity.getId());
		if (persistentEntity == null) {
			throw new IllegalStateException("There is no" + getMappedClass() + " with id = " + entity.getId());
		}
		persistentEntity.updateFromRequest(entity);
		persistenceAdapter.save(persistentEntity);
		return persistentEntity;
	}

	public abstract Entity updateFromRequest(Entity entity);

	@POST @Path("delete")
    public Entity delete(Entity entity) {
		persistenceAdapter.delete(entity);
		return entity;
	}

}
